package com.zwh.modbus.modbus4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;

import com.zwh.modbus.modbus4j.modbusrtu.SerialParameters;
import com.zwh.modbus.modbus4j.param.ConnectDeviceParam;
import com.zwh.modbus.modbus4j.param.ConnectDevicePointParam;
import com.zwh.modbus.modbus4j.param.TConnectDevicePoint;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.serotonin.modbus4j.BatchRead;
import com.serotonin.modbus4j.BatchResults;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.zwh.modbus.modbus4j.param.TConnectDevice;

import lombok.extern.slf4j.Slf4j;

/**
 * @Author ody
 * @Description //TODO
 * @Date 2023/6/28 14:38
 **/
@Slf4j
public class ModbusService extends AbstractDeviceProtocolService implements DeviceProtocolService {

    private ModbusMaster master = null;

    private TConnectDevice connectDevice;

    private Integer num = 50;

    private String key = null;

    public ModbusService(TConnectDevice connectDevice, RemoteConnectDeviceService remoteConnectDeviceService,
                         ThreadPoolTaskExecutor storageDataThreadPool){
        super(connectDevice,remoteConnectDeviceService,storageDataThreadPool);
        this.connectDevice = connectDevice;
    }

    @Override
    public void init() throws Exception {
        key = connectDevice.getParam1();
        String basicParamStr = (String)connectDevice.getBasicParam();
        ConnectDeviceParam basicParam = JSON.parseObject(basicParamStr, ConnectDeviceParam.class);
        String deviceType = connectDevice.getDeviceType();
        if(deviceType.equals("Modbus-RTU")){
            master = ModbusMasterCache.getModbusMaster(key);
            if(master != null){
                ModbusMasterCache.setCount(key);
                return;
            }
            SerialParameters parameters = new SerialParameters(connectDevice.getParam1(),Integer.valueOf(connectDevice.getParam2()),
                    0,0,basicParam.getDataBit(),basicParam.getStopBit(),basicParam.getCheckBit());
            master = ModbusUtils.getModbusFactory().createRtuMaster(parameters);
        }else if(deviceType.equals("Modbus-ASCII")){
            master = ModbusMasterCache.getModbusMaster(key);
            if(master != null){
                ModbusMasterCache.setCount(key);
                return;
            }
            //还需判断串口资源是否被占用，占用就返回当前串口已被实例化的master
            SerialParameters parameters = new SerialParameters(connectDevice.getParam1(),Integer.valueOf(connectDevice.getParam2()),
                    0,0,basicParam.getDataBit(),basicParam.getStopBit(),basicParam.getCheckBit());
            master = ModbusUtils.getModbusFactory().createAsciiMaster(parameters);
        }else if(deviceType.equals("Modbus-TCP")){
            /*key = connectDevice.getParam1() + "_" + connectDevice.getParam2();
            master = ModbusMasterCache.getModbusMaster(key);
            if(master != null){
                ModbusMasterCache.setCount(key);
                return;
            }*/
            key = null;
            IpParameters params = new IpParameters();
            params.setEncapsulated(basicParam.getIsVerify());
            params.setHost(connectDevice.getParam1());
            params.setPort(Integer.valueOf(connectDevice.getParam2()));
            // TCP 协议
            master = ModbusUtils.getModbusFactory().createTcpMaster(params,true);
        }else {
            throw new RuntimeException("modbus传输方式有误！");
        }
        master.setTimeout(basicParam.getTimeout());
        master.setRetries(1);
        master.init();
        if(key != null){
            ModbusMasterCache.setModbusMaster(key,master);
        }
    }

    @Override
    public JSONObject read(Object var){
        if(isCancel){
            return null;
        }
        List<TConnectDevicePoint> devicePoints = connectDevice.getConnectDevicePoints();
        int deviceAddr = Integer.parseInt(connectDevice.getDeviceAddr());
        int total = devicePoints.size();
        JSONObject data = new JSONObject(total);
        //算出循环次数
        int count = total / num;
        int size = num;
        for (int j = 0; j <= count ; j++) {
            if(total > (j + 1) * num){
                size = (j + 1) * num;
            }else {
                size = total;
            }
            try {
                //确保每次读取前后互不影响
                BatchRead<String> batch = new BatchRead<>();
                for(int i = j * num; i < size; i++) {
                    TConnectDevicePoint point = devicePoints.get(i);
                    String pointIden = point.getPointIden();
                    ConnectDevicePointParam pointParam = point.getParam();
                    if(pointParam == null){
                        pointParam = JSON.parseObject((String)point.getBasicParam(), ConnectDevicePointParam.class);
                        point.setParam(pointParam);
                    }
                    String addressType = pointParam.getAddressType();
                    String type = pointParam.getType();
                    switch (addressType){
                        case "01" :
                            batch.addLocator(pointIden,BaseLocator.coilStatus(deviceAddr,point.getRegisterAddress()));
                            break;
                        case "02" :
                            batch.addLocator(pointIden,BaseLocator.inputStatus(deviceAddr, point.getRegisterAddress()));
                            break;
                        case "03" :
                            if("1".equals(type)){
                                type = "2";
                            }
                            batch.addLocator(pointIden,BaseLocator.holdingRegister(deviceAddr,point.getRegisterAddress(),
                                    Integer.parseInt(type)));
                            break;
                        case "04" :
                            if("1".equals(type)){
                                type = "2";
                            }
                            batch.addLocator(pointIden, BaseLocator.inputRegister(deviceAddr,point.getRegisterAddress(),
                                    Integer.parseInt(type)));
                            break;
                        default:
                            break;
                    }
                }
                batch.setContiguousRequests(false);
                BatchResults<String> results = master.send(batch);
                for(int i = j * num; i < size; i++) {
                    TConnectDevicePoint point = devicePoints.get(i);
                    Object value = results.getValue(point.getPointIden());
                    ConnectDevicePointParam param = point.getParam();
                    if("1".equals(param.getType())){
                        Integer va = (Integer)value;
                        int bit = (va >> param.getOffect()) & 1;
                        data.put(point.getPointIden(),bit);
                        continue;
                    }
                    if(value instanceof Integer){
                        Integer va = (Integer)value;
                        BigDecimal v1 = new BigDecimal(va);
                        BigDecimal v2 = new BigDecimal(param.getMultiplier());
                        int scale = param.getDecimal();
                        BigDecimal bigDecimal = v1.multiply(v2).setScale(scale,RoundingMode.HALF_UP);
                        data.put(point.getPointIden(),bigDecimal);
                    }else {
                        data.put(point.getPointIden(),value);
                    }
                }
            }catch (Exception e){
                log.error("该连接设备：{}，本轮读取采集数据失败，原因为：{}",connectDevice.getDeviceIden(),e.getMessage(),e);
            }
        }
        super.updateDeviceStatus(data);
        super.storageCollectData(data);
        return data;
    }

    @Override
    public void write(TConnectDevicePoint point,Object value) throws Exception{
        ConnectDevicePointParam pointParam = JSON.parseObject((String)point.getBasicParam(), ConnectDevicePointParam.class);
        String controlCode = pointParam.getControlCode();
        switch (controlCode) {
            case "15":
            case "05":
                // 创建请求
                Boolean v1 = Boolean.valueOf((String)value);
                BaseLocator<Boolean> locator1 = BaseLocator.coilStatus(Integer.parseInt(connectDevice.getDeviceAddr()),pointParam.getRegisterAddress());
                master.setValue(locator1,v1);
                break;
            case "16":
            case "06":
                int dataType = Integer.parseInt(pointParam.getType());
                BigDecimal v2 = new BigDecimal((String)value);
                BigDecimal v3 = new BigDecimal(pointParam.getMultiplier());
                BigDecimal result = v2.divide(v3,0, RoundingMode.HALF_UP);
                BaseLocator<Number> locator = BaseLocator.holdingRegister(Integer.parseInt(connectDevice.getDeviceAddr()),pointParam.getRegisterAddress(),dataType);
                master.setValue(locator,result);
                break;
            default:
                break;
        }
    }

    @Override
    public void distroy() {
        try {
            if(key == null){
                if(master != null){
                    master.destroy();
                }
                return;
            }
            if(ModbusMasterCache.removeModbusMaster(key)){
                if(master != null){
                    master.destroy();
                }
            }
        }catch (Exception e){
            log.error("modbus 关闭失败，原因为：{}",e.getMessage(),e);
        }
    }
}
